import os
import uuid
import base64
import json
from datetime import datetime
import pytz
from flask import Flask, render_template, Response, request, jsonify, redirect, url_for, session, flash, send_from_directory
from werkzeug.utils import secure_filename
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy
from PIL import Image, ImageDraw, ImageFont
import cv2
import requests
from openai import OpenAI
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'a-very-secure-and-long-secret-key-that-is-not-hardcoded')
app.config['UPLOAD_FOLDER'] = 'uploads/'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 初始化数据库
db = SQLAlchemy(app)

# 初始化阿里云客户端 (用于deepfake检测)
try:
    client = OpenAI(
        api_key=os.getenv("DASHSCOPE_API_KEY"),
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )
    if not os.getenv("DASHSCOPE_API_KEY"):
        print("警告: 未找到 DASHSCOPE_API_KEY。Deepfake检测功能将不可用。")
except Exception as e:
    print(f"初始化OpenAI客户端失败: {e}")
    client = None

# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs('./static', exist_ok=True)

# 开启摄像头
cap = cv2.VideoCapture(0)

# 加载人脸检测模型
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

# 百度AI API配置
API_KEY = os.getenv("ARK_API_KEY")
SECRET_KEY = os.getenv("ARK_SECRET_KEY")

if not API_KEY or not SECRET_KEY:
    print("警告: 未找到百度API密钥或密钥不完整。活体检测功能将不可用。")

# 数据库模型
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(256), nullable=False)
    uploads = db.relationship('Upload', backref='uploader', lazy=True)

class Upload(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    original_filename = db.Column(db.String(200), nullable=False)
    secure_filename = db.Column(db.String(200), unique=True, nullable=False)
    analysis_result = db.Column(db.Text, nullable=True)
    def beijing_now(self):
        return datetime.now(pytz.timezone('Asia/Shanghai'))
    timestamp = db.Column(db.DateTime, nullable=False, default=beijing_now)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

# 辅助函数
def allowed_file(filename):
    ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def image_to_base64(image_path):
    try:
        with open(image_path, "rb") as image_file:
            encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
            mime_type = "image/jpeg" if image_path.lower().endswith(('.jpg', '.jpeg')) else "image/png"
            return f"data:{mime_type};base64,{encoded_string}"
    except Exception:
        return None

# Deepfake检测函数
def analyze_image_api(image_path):
    """调用大模型API分析图片，返回一个包含结果和理由的字典"""
    if not client:
        return {"结果": "API客户端未初始化", "理由": "未能连接到模型服务，请检查服务器配置。"}

    base64_image = image_to_base64(image_path)
    if not base64_image:
        return {"结果": "图片读取失败", "理由": "无法读取或编码服务器上的图片文件。"}

    image_data = {"type": "image_url", "image_url": {"url": base64_image}}
    prompt_text = (
        "你是一位图像分析专家，核心任务是检测图像是否经过深度伪造（Deepfake）技术处理的伪造图片。"
        "请仔细分析这张图片，分析其是否由深度伪造得到，主要判断依据为：是否存在不正常的肤色变化和五官轮廓，正常的修图不属于深度伪造"
        "请严格以JSON格式返回你的分析结果，不要包含JSON格式之外的任何额外说明文字。"
        "JSON必须包含两个键："
        "1. '结果': 其值必须是'判定为真实图片'、'疑似深度伪造'或'高度可信为深度伪造'中的一个。"
        "2. '理由': 以一段话的格式，说明你的判断依据。"
    )

    try:
        completion = client.chat.completions.create(
            model="qwen-vl-max",
            messages=[{"role": "user", "content": [image_data, {"type": "text", "text": prompt_text}]}]
        )
        if completion.choices:
            message_content = completion.choices[0].message.content
            try:
                if "```json" in message_content:
                    json_str = message_content.split("```json")[1].split("```")[0].strip()
                else:
                    json_str = message_content
                return json.loads(json_str)
            except (json.JSONDecodeError, IndexError, TypeError):
                return {"结果": "判定为真实图片", "理由": "模型未能返回有效的JSON格式分析，系统默认此图片为真实图片（安全回退机制）。"}
        else:
            return {"结果": "判定为真实图片", "理由": "模型API未返回任何有效响应，系统默认此图片为真实图片（安全回退机制）。"}
    except Exception as e:
        print(f"API Call Error: {e}")
        return {"结果": "判定为真实图片", "理由": f"调用模型API时发生错误: {e}。系统默认此图片为真实图片（安全回退机制）。"}

# 活体检测相关函数
def get_access_token():
    url = "https://aip.baidubce.com/oauth/2.0/token"
    params = {
        "grant_type": "client_credentials",
        "client_id": API_KEY,
        "client_secret": SECRET_KEY
    }
    response = requests.post(url, params=params)
    token_data = response.json()
    return token_data.get("access_token")

def get_file_content_as_base64(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

def face_attributes_and_location(access_token, image_base64):
    url = f"https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token={access_token}"
    payload = json.dumps({
        "image": image_base64,
        "image_type": "BASE64",
        "face_field": "age,gender,emotion"
    })
    headers = {'Content-Type': 'application/json'}
    response = requests.post(url, headers=headers, data=payload)
    face_data = response.json()
    return face_data

def face_liveness_check(access_token, image_base64):
    url = f"https://aip.baidubce.com/rest/2.0/face/v3/faceverify?access_token={access_token}"
    payload = json.dumps([{"image": image_base64, "image_type": "BASE64"}])
    headers = {'Content-Type': 'application/json'}
    response = requests.post(url, headers=headers, data=payload)
    liveness_data = response.json()
    return liveness_data

def draw_details(image, face_data, liveness_data):
    draw = ImageDraw.Draw(image)
    try:
        font = ImageFont.truetype("arial.ttf", 20)
    except IOError:
        font = ImageFont.load_default()
    
    for face in face_data.get('result', {}).get('face_list', []):
        box = [
            face['location']['left'],
            face['location']['top'],
            face['location']['left'] + face['location']['width'],
            face['location']['top'] + face['location']['height']
        ]
        draw.rectangle(box, outline='red', width=5)
        attributes_text = f"Age: {face['age']}, Gender: {face['gender']['type']}, Emotion: {face['emotion']['type']}"
        liveness_text = f"Liveness: {liveness_data['result']['face_liveness']}"
        draw.text((face['location']['left'], face['location']['top'] - 60), liveness_text, fill='blue', font=font)
        draw.text((face['location']['left'], face['location']['top'] - 30), attributes_text, fill='red', font=font)

def crop_to_4_3(image):
    height, width, _ = image.shape
    target_width = int(height * 3 / 4)
    if target_width > width:
        pad = (target_width - width) // 2
        image = cv2.copyMakeBorder(image, 0, 0, pad, pad, cv2.BORDER_CONSTANT, value=(0, 0, 0))
    elif target_width < width:
        crop = (width - target_width) // 2
        image = image[:, crop:crop + target_width, :]
    return image

def gen_frames():
    while True:
        success, frame = cap.read()
        if not success:
            break
        else:
            frame = cv2.flip(frame, 1)
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            faces = face_cascade.detectMultiScale(gray, 1.1, 4)
            for (x, y, w, h) in faces:
                cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
            ret, buffer = cv2.imencode('.jpg', frame)
            frame = buffer.tobytes()
            yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

# 路由定义
@app.route('/')
def index():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    return render_template('index.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if User.query.filter_by(username=username).first():
            flash('该用户名已被注册。', 'error')
            return redirect(url_for('register'))
        hashed_password = generate_password_hash(password)
        new_user = User(username=username, password_hash=hashed_password)
        db.session.add(new_user)
        db.session.commit()
        flash('注册成功！请登录。', 'success')
        return redirect(url_for('login'))
    return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and check_password_hash(user.password_hash, password):
            session.clear()
            session['user_id'] = user.id
            return redirect(url_for('index'))
        else:
            flash('用户名或密码错误。', 'error')
            return redirect(url_for('login'))
    return render_template('login.html')

@app.route('/logout')
def logout():
    session.clear()
    flash('您已成功登出。', 'info')
    return redirect(url_for('login'))

@app.route('/video_feed')
def video_feed():
    return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/recognize', methods=['POST'])
def recognize():
    if 'user_id' not in session:
        return jsonify({"error": "请先登录"}), 401
    
    try:
        success, frame = cap.read()
        if not success:
            return jsonify({"error": "Failed to capture image from camera."}), 500
        
        frame = crop_to_4_3(frame)
        image_path = './current_frame.jpg'
        cv2.imwrite(image_path, frame)
        print(f"图片已保存到: {image_path}")
        
        try:
            print("开始活体检测...")
            access_token = get_access_token()
            if not access_token:
                return jsonify({"error": "Failed to retrieve access token."}), 500
            
            print("获取access token成功")
            
            image_base64 = get_file_content_as_base64(image_path)
            print("图片转换为Base64成功")
            
            face_data = face_attributes_and_location(access_token, image_base64)
            print("人脸检测完成")
            
            liveness_data = face_liveness_check(access_token, image_base64)
            print("活体检测完成")
            
            image = Image.open(image_path)
            draw_details(image, face_data, liveness_data)
            
            output_path = './static/liveness_result.jpg'
            image.save(output_path)
            print(f"活体检测结果已保存到: {output_path}")
            
            return jsonify({
                "result": "活体检测完成",
                "output_image": "/static/liveness_result.jpg",
                "face_data": face_data,
                "liveness_data": liveness_data
            })
            
        except Exception as e:
            print(f"活体检测过程中发生错误: {str(e)}")
            import traceback
            traceback.print_exc()
            return jsonify({"error": f"活体检测失败: {str(e)}"}), 500
            
    except Exception as e:
        print(f"recognize函数发生错误: {str(e)}")
        import traceback
        traceback.print_exc()
        return jsonify({"error": f"Internal server error: {str(e)}"}), 500

@app.route('/uploads/<string:filename>')
def serve_upload(filename):
    if 'user_id' not in session:
        return "Access denied", 403
    upload_record = Upload.query.filter_by(secure_filename=filename).first_or_404()
    if upload_record.user_id != session['user_id']:
        return "Access denied", 403
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

@app.route('/upload', methods=['GET', 'POST'])
def upload_page():
    if 'user_id' not in session:
        return redirect(url_for('login'))

    user = User.query.get(session['user_id'])
    user_uploads = Upload.query.filter_by(user_id=user.id).order_by(Upload.timestamp.desc()).all()

    if request.method == 'POST':
        if 'file' not in request.files or not request.files['file'].filename:
            flash('未选择文件。', 'warning')
            return redirect(request.url)

        file = request.files['file']
        if file and allowed_file(file.filename):
            original_fn = secure_filename(file.filename)
            extension = original_fn.rsplit('.', 1)[1].lower()
            unique_fn = f"{uuid.uuid4()}.{extension}"
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_fn)
            file.save(filepath)

            try:
                img = Image.open(filepath)
                img.verify()
                img = Image.open(filepath)
                img.save(filepath)
            except Exception:
                os.remove(filepath)
                flash('上传的文件不是一个有效的图片格式，请重新上传。', 'danger')
                return redirect(url_for('upload_page'))

            analysis_dict = analyze_image_api(filepath)
            analysis_result_text = f"结果: {analysis_dict.get('结果', '未知')}\n理由: {analysis_dict.get('理由', '无')}"
            
            new_upload = Upload(
                original_filename=original_fn, 
                secure_filename=unique_fn, 
                analysis_result=analysis_result_text, 
                user_id=session['user_id']
            )
            db.session.add(new_upload)
            db.session.commit()

            flash('图片上传并分析成功！', 'success')
            return redirect(url_for('upload_page'))

    return render_template('upload.html', uploads=user_uploads)

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True, port=5000)
